package com.test.nio.channel.tcp;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

/**
 *
 * 为什么使用Selector?
 仅用单个线程来处理多个Channels的好处是，只需要更少的线程来处理通道。
 事实上，可以只用一个线程处理所有的通道。对于操作系统来说，
 线程之间上下文切换的开销很大，而且每个线程都要占用系统的一些资源（如内存）。
 因此，使用的线程越少越好。
 */
public class SelectorOpen {

    private static int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
    private static int interestSet2 = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

    public static SocketChannel createSocketChannel(String hostName, int port)
            throws IOException {
        SocketChannel sChannel = SocketChannel.open();
        sChannel.configureBlocking(false);
        sChannel.connect(new InetSocketAddress(hostName, port));
        return sChannel;
    }

    public static void printMsg(SocketChannel channel,String msg) throws Exception{


        ByteBuffer buf = ByteBuffer.allocateDirect(1024);
        while (!channel.finishConnect()) {
            System.out.println("等待非阻塞连接建立....");
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        buf.put(msg.getBytes("UTF-8"));
        buf.flip();
        channel.write(buf);
        buf.clear();
        int numBytesRead;
        while ((numBytesRead = channel.read(buf)) != -1) {
            if (numBytesRead == 0) {
                // 如果没有数据，则稍微等待一下
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                continue;
            }
            // 转到最开始
            buf.flip();
            String receivedString = "";
            while (buf.remaining() > 0) {
                //接收服务端返回的信息
                String temp = Charset.forName("UTF-8").newDecoder()
                        .decode(buf).toString();
                receivedString += temp;
            }
            System.out.println(receivedString);
            // 也可以转化为字符串，不过需要借助第三个变量了。
            // buf.get(buff, 0, numBytesRead);
            // System.out.println(new String(buff, 0, numBytesRead, "UTF-8"));
            // 复位，清空
            buf.clear();
        }

    }

    public static void main(String[] args){

        try {
            SocketChannel channel1 =  createSocketChannel("localhost",1978);


            /**
             * 注册到selector，FileChannel不支持，因为不支持非阻塞模式
             *
             *
             * 注意register()方法的第二个参数。这是一个“interest集合”，
             * 意思是在通过Selector监听Channel时对什么事件感兴趣。
             * 可以监听四种不同类型的事件：

             Connect
             Accept
             Read
             Write
             通道触发了一个事件意思是该事件已经就绪。
             所以，某个channel成功连接到另一个服务器称为“连接就绪”。
             一个server socket channel准备好接收新进入的连接称为“接收就绪”。
             一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”
             * SelectionKey.OP_CONNECT
               SelectionKey.OP_ACCEPT
               SelectionKey.OP_READ
               SelectionKey.OP_WRITE

             如果你对不止一种事件感兴趣，那么可以用“位或”操作符将常量连接起来，如下：
             int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

             */

            printMsg(channel1,"Hello , I am Channel1 ");


        } catch (Exception e) {
            e.printStackTrace();
        }

    }

}
